/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.portlet.container.properties;

import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableSet;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.apereo.portal.portlet.om.IPortletWindow;
import org.apereo.portal.portlet.om.IPortletWindowId;
import org.apereo.portal.url.IPortalRequestUtils;
import org.apereo.portal.utils.Populator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Portlet property manager that just translates properties to/from the portal request attributes
 */
@Service
public class PropertyToAttributePropertiesManager extends BaseRequestPropertiesManager {
    private IPortalRequestUtils portalRequestUtils;
    private BiMap<String, String> propertyToAttributeMappings = ImmutableBiMap.of();
    private BiMap<String, String> attributeToPropertyMappings = ImmutableBiMap.of();
    private Set<String> nonNamespacedProperties = Collections.emptySet();

    /**
     * Map of portlet property names to attribute names, if the value is null the key will be used
     * for both the property and attribute name
     */
    @Resource(name = "portletPropertyToAttributeMappings")
    public void setPropertyMappings(Map<String, String> propertyMappings) {
        this.propertyToAttributeMappings = ImmutableBiMap.copyOf(propertyMappings);
        this.attributeToPropertyMappings = this.propertyToAttributeMappings.inverse();
    }

    /**
     * Properties that should not be namespaced with the portlet's windowId when stored as request
     * attributes
     */
    @Resource(name = "nonNamespacedPortletProperties")
    public void setNonNamespacedProperties(Set<String> nonNamespacedProperties) {
        this.nonNamespacedProperties = ImmutableSet.copyOf(nonNamespacedProperties);
    }

    @Autowired
    public void setPortalRequestUtils(IPortalRequestUtils portalRequestUtils) {
        this.portalRequestUtils = portalRequestUtils;
    }

    @Override
    public boolean addResponseProperty(
            HttpServletRequest portletRequest,
            IPortletWindow portletWindow,
            String property,
            String value) {
        if (this.propertyToAttributeMappings.isEmpty() && this.nonNamespacedProperties.isEmpty()) {
            return false;
        }

        final HttpServletRequest portalRequest =
                this.portalRequestUtils.getOriginalPortalRequest(portletRequest);

        final String attributeName = getAttributeName(portletWindow, property);

        final Object existingValue = portalRequest.getAttribute(attributeName);
        if (!(existingValue instanceof List)) {
            this.logger.warn(
                    "Attribute {} for property {} exists but is NOT a List, it will be replaced",
                    attributeName,
                    property);
            this.setResponseProperty(portletRequest, portletWindow, property, value);
            return true;
        }

        logger.debug("Adding property {} as attribute {}", property, attributeName);

        @SuppressWarnings("unchecked")
        final List<String> values = (List<String>) existingValue;
        values.add(value);
        portalRequest.setAttribute(attributeName, values);
        return true;
    }

    @Override
    public boolean setResponseProperty(
            HttpServletRequest portletRequest,
            IPortletWindow portletWindow,
            String property,
            String value) {
        if (this.propertyToAttributeMappings.isEmpty() && this.nonNamespacedProperties.isEmpty()) {
            return false;
        }

        final HttpServletRequest portalRequest =
                this.portalRequestUtils.getOriginalPortalRequest(portletRequest);

        final String attributeName = getAttributeName(portletWindow, property);

        logger.debug("Setting property {} as attribute {}", property, attributeName);

        final List<String> values = new LinkedList<String>();
        values.add(value);
        portalRequest.setAttribute(attributeName, values);

        return true;
    }

    protected String getAttributeName(IPortletWindow portletWindow, String property) {
        final String mappedAttributeName = this.propertyToAttributeMappings.get(property);
        final String attributeName;
        if (mappedAttributeName == null) {
            attributeName = property;
        } else {
            attributeName = mappedAttributeName;
        }

        if (this.nonNamespacedProperties.contains(property)) {
            return attributeName;
        }

        final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();
        return portletWindowId.getStringId() + attributeName;
    }

    @Override
    public <P extends Populator<String, String>> void populateRequestProperties(
            HttpServletRequest portletRequest,
            IPortletWindow portletWindow,
            P propertiesPopulator) {
        if (this.propertyToAttributeMappings.isEmpty() && this.nonNamespacedProperties.isEmpty()) {
            return;
        }

        final HttpServletRequest portalRequest =
                this.portalRequestUtils.getOriginalPortalRequest(portletRequest);
        final String windowIdStr = portletWindow.getPortletWindowId().getStringId();

        for (@SuppressWarnings("unchecked")
                final Enumeration<String> attributeNames = portalRequest.getAttributeNames();
                attributeNames.hasMoreElements(); ) {
            final String fullAttributeName = attributeNames.nextElement();
            final String propertyName = getPropertyName(windowIdStr, fullAttributeName);
            if (propertyName == null) {
                continue;
            }

            logger.debug(
                    "Found portal request attribute {} returning as property {}",
                    fullAttributeName,
                    propertyName);

            final Object value = portalRequest.getAttribute(fullAttributeName);
            convertValue(propertyName, value, propertiesPopulator);
        }
    }

    /** Convert a request attribute name to a portlet property name */
    private String getPropertyName(final String windowIdStr, final String fullAttributeName) {
        final String attributeName;
        if (this.nonNamespacedProperties.contains(fullAttributeName)) {
            attributeName = fullAttributeName;
        } else if (fullAttributeName.startsWith(windowIdStr)) {
            attributeName = fullAttributeName.substring(windowIdStr.length());
        } else {
            return null;
        }

        final String mappedPropertyName = this.attributeToPropertyMappings.get(attributeName);
        if (mappedPropertyName == null) {
            logger.warn(
                    "Attribute {} found that matches the portlet window ID but it is not listed in the propertyMappings or nonNamespacedProperties and will not be returned to the portlet",
                    attributeName);
            return null;
        }

        return mappedPropertyName;
    }

    protected <P extends Populator<String, String>> void convertValue(
            String name, Object value, P propertiesPopulator) {
        if (value == null) {
            return;
        }

        if (value instanceof Collection) {
            for (final Object obj : (Collection<?>) value) {
                propertiesPopulator.put(name, String.valueOf(obj));
            }
            return;
        }

        if (value.getClass().isArray()) {
            final int len = Array.getLength(value);
            for (int i = 0; i < len; i++) {
                propertiesPopulator.put(name, String.valueOf(Array.get(value, i)));
            }
            return;
        }

        propertiesPopulator.put(name, String.valueOf(value));
    }
}
